﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using ProjectRoughWork.Engine.SoundManager;

namespace ProjectRoughWork
{
    class GameplayScreen : GameScreen
    {
        /// <summary>
        /// Default Constructor. Initializes the level to be drawn
        /// </summary>

        public GameplayScreen()
        {

            pcsMng = new ProcessManager();
            actorMap = new Dictionary<int, IActor>();
            currentLevel = new Level();
            lastActorID = 0;
        }

        /// <summary>
        /// 1-Parameter Constructor to initialize the player's squad
        /// </summary>
        /// <param name="initPlayerSquad">The player's squad</param>

        public GameplayScreen(SquadActor[] initPlayerSquad)
        {
            pcsMng = new ProcessManager();

            //Initialize data members

            lastActorID = 0;
            actorMap = new Dictionary<int, IActor>();
            currentLevel = new Level();

            //Retrieve the squad's starting position from the Level

            Vector3[,] initPositions = currentLevel.InitialSquadPositions;

            //Loop through squad members to set their positions

            for (int index = 0; index < 3; index++)
            {
                
                initPlayerSquad[index].setPosition(initPositions[0, index]);
                initPlayerSquad[index].Player = true;
                initPlayerSquad[index].ModelType = "PlayerMarineBlue";
                AddActor(initPlayerSquad[index]);
            }

            initPlayerSquad[0].User = true;
            playerSquad = new Squad(initPlayerSquad, this, true);
            SetUpEnemySquad();

            // Begin playing background music
            SoundManager.GetInstance().playBackgroundMusic(SoundManager.BGM_GAME);

        }

        /// <summary>
        /// Initializes the enemy squad with random units
        /// </summary>

        public void SetUpEnemySquad()
        {
            rand = new Random();
            SquadActor[] tempSquad = new SquadActor[3];
            int randomNumber;

            Vector3[,] initPositions = currentLevel.InitialSquadPositions;

            //Randomly creates units to fill out the squad
            SkinnedModel.firstRun = false;
            for (int index = 0; index < 3; index++)
            {
                //Generate random number

                randomNumber = rand.Next(0, 3);

                if (randomNumber == 0)
                {
                    tempSquad[index] = new Soldier("PlayerMarineRed");
                    tempSquad[index].setPosition(initPositions[1, index]);
                    //AddActor(tempSquad[index]);
                }
                else if (randomNumber == 1)
                {
                    tempSquad[index] = new Skirmisher("PlayerMarineRed");
                    tempSquad[index].setPosition(initPositions[1, index]);
                    //AddActor(tempSquad[index]);
                }
                else if (randomNumber == 2)
                {
                    tempSquad[index] = new Demolition("PlayerMarineRed");
                    tempSquad[index].setPosition(initPositions[1, index]);
                    //AddActor(tempSquad[index]);
                }
                tempSquad[index].Player = false;

                //Enemy Squad's model type is red
                //tempSquad[index].ModelType = "PlayerMarineRed";
                AddActor(tempSquad[index]);
            }

            enemySquad = new Squad(tempSquad, this, false);
        }

        public Squad PlayerSquad
        {
            get { return playerSquad; }
        }

        /// <summary>
        /// Tests given actor against all other actors for coliisions
        /// </summary>
        /// <param name="actorID">The ID of the actor to be tested</param>
        /// <returns>Returns true if there's a collision, False if no collision</returns>

        public IActor CheckNerfForCollision(int actorID, int shooterID)
        {
            BaseActor actorTest = (BaseActor)actorMap[actorID];

            foreach (KeyValuePair<int,IActor> pair in actorMap)
            {
                 
                BaseActor actor = (BaseActor)pair.Value;
                if ( (actor.getID() != actorID) &&                                                  //Not the actor being tested Check
                    (actor.getID() != shooterID) &&                                                 //Not the Shooter Check
                    (actor.getType().Equals("SquadActor")) &&                                       //Squad Actor Check
                    (((SquadActor)actor).Player != ((SquadActor)actorMap[shooterID]).Player )       //Friendly Fire Check
                    )
                {
                    if (actorTest.BSphere.Intersects(actor.BSphere))
                    {
                        return actor;
                    }
                }
            }

            return null;
        }

        public void CheckForGameOver()
        {
            bool gameOver = true;

            for (int index = 0; index < 3; index++)
            {
                if (!playerSquad.SquadUnits[index].isDead())
                {
                    gameOver = false;
                }
            }

            if (!gameOver)
            {
                gameOver = true;
                for (int index = 0; index < 3; index++)
                {
                    if (!enemySquad.SquadUnits[index].isDead())
                    {
                        gameOver = false;
                    }
                }
            }

            if (gameOver)
            {
                EventChangeGameState changeEvt = new EventChangeGameState(ProjectGameLogic.GameState.Results);
                EventManager.GetInstance().VTriggerEvent(changeEvt);
            }
        }

        public SquadActor FindClosestEnemy(SquadActor actor, bool isPlayer)
        {
            SquadActor closest = null;
            float smallestDistance = 99999;
            float tempDistance;

            for (int index = 0; index < 3; index++)
            {
                if ((isPlayer) && 
                    (!enemySquad.SquadUnits[index].isDead()) 
                    )
                {
                    tempDistance = (enemySquad.SquadUnits[index].getPosition() - actor.getPosition()).Length();

                    if (tempDistance < smallestDistance)
                    {
                        closest = enemySquad.SquadUnits[index];
                        smallestDistance = tempDistance;
                    }
                }
                else if ( (!isPlayer)&& (!playerSquad.SquadUnits[index].isDead())
                    )
                {
                    tempDistance = (playerSquad.SquadUnits[index].getPosition() - actor.getPosition()).Length();

                    if (tempDistance < smallestDistance)
                    {
                        closest = playerSquad.SquadUnits[index];
                        smallestDistance = tempDistance;
                    }
                }
            }

            return closest;
        }

        public bool CheckIfTooCloseToEnemy(Vector3 actorPosition, int range, bool player)
        {
            if (!player)
            {
                for (int index = 0; index < 3; index++)
                {
                    if (Math.Abs((actorPosition - playerSquad.SquadUnits[index].Position).Length()) < range)
                    {
                        return true;
                    }
                }
            }
            else
            {
                for (int index = 0; index < 3; index++)
                {
                    if (Math.Abs((actorPosition - enemySquad.SquadUnits[index].Position).Length()) < range)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public Rectangle CalculateOpposingSquadInfluence(bool player)
        {
            Rectangle influenceBoundingBox;
            Vector3 center = Vector3.Zero;
            float largestDistance = 0;
            float tempDistance = 0;

            for (int index = 0; index < 3; index++)
            {
                if (player)
                {
                    center += playerSquad.SquadUnits[index].getPosition();
                }
                else
                {
                    center += enemySquad.SquadUnits[index].getPosition();
                }
            }

            center /= 3;

            for (int index = 0; index < 3; index++)
            {
                if (player)
                {
                    tempDistance = Math.Abs((playerSquad.SquadUnits[index].getPosition() - center).Length());
                }
                else
                {
                    tempDistance = Math.Abs((enemySquad.SquadUnits[index].getPosition() - center).Length());
                }

                if (tempDistance > largestDistance)
                {
                    largestDistance = tempDistance;
                }
            }

            influenceBoundingBox = new Rectangle( (int)(center.X - largestDistance / 2), (int)(center.Z - largestDistance / 2), (int)largestDistance, (int)largestDistance);

            return influenceBoundingBox;
        }

        public Vector3 FindNearestCover(Vector3 actorPosition)
        {
            return (currentLevel.FindNearestCover(actorPosition)).Value;
        }

        /// <summary>
        /// Processes input from the keyboard for the gameplay screen
        /// </summary>
        /// <param name="inputKey">The state keyboard input</param>

        public override void HandleInput(KeyboardState inputKey)
        {
            if ((inputKey.IsKeyDown(Keys.Escape)) && (lastKeyInput.IsKeyUp(Keys.Escape)))
            {
                PauseScreen pauseScreen = new PauseScreen();
                ScreenManager.GetInstance().AddScreen(pauseScreen);
                SoundManager.GetInstance().pauseBackgroundMusic();
            }
            if (!playerSquad.SquadUnits[0].isDead())
            {
                if (inputKey.IsKeyDown(Keys.Up))
                {
                    SquadActor user = playerSquad.SquadUnits[0];
                    Matrix rotMat = Matrix.CreateRotationY(user.getRotation());
                    Vector3 dir = new Vector3(1, 0, 0);
                    dir = Vector3.Transform(dir, rotMat);
                    EventMoveActor mvEvt = new EventMoveActor(user.getID(), user.getPosition() + (dir * user.MaxVelocity));
                    EventManager.GetInstance().VQueueEvent(mvEvt);
                    user.Moving = true;
                }
                else if (inputKey.IsKeyDown(Keys.Down))
                {
                    SquadActor user = playerSquad.SquadUnits[0];
                    Matrix rotMat = Matrix.CreateRotationY(user.getRotation());
                    Vector3 dir = new Vector3(1, 0, 0);
                    dir = Vector3.Transform(dir, rotMat);
                    EventMoveActor mvEvt = new EventMoveActor(user.getID(), user.getPosition() - dir * user.MaxVelocity);
                    EventManager.GetInstance().VQueueEvent(mvEvt);
                    user.Moving = true;
                }
                else
                    playerSquad.SquadUnits[0].Moving = false;

                if (inputKey.IsKeyDown(Keys.Left))
                {
                    SquadActor user = playerSquad.SquadUnits[0];
                    user.setRotation(user.getRotation() + 0.05f);

                    /*
                    Vector3 dir = new Vector3(-1, 0, 0);
                    EventMoveActor mvEvt = new EventMoveActor(user.getID(), user.getPosition() + dir * user.MaxVelocity);
                    EventManager.GetInstance().VQueueEvent(mvEvt);
                     */
                }
                if (inputKey.IsKeyDown(Keys.Right))
                {
                    SquadActor user = playerSquad.SquadUnits[0];
                    user.setRotation(user.getRotation() + -0.05f);

                    /*
                    Vector3 dir = new Vector3(1, 0, 0);
                    EventMoveActor mvEvt = new EventMoveActor(user.getID(), user.getPosition() + dir* user.MaxVelocity);
                    EventManager.GetInstance().VQueueEvent(mvEvt); */
                }
                if (inputKey.IsKeyDown(Keys.Space))
                {
                    SquadActor user = playerSquad.SquadUnits[0];
                    Matrix rotMat = Matrix.CreateRotationY(user.getRotation());
                    Vector3 dir = new Vector3(1, 0, 0);
                    dir = Vector3.Transform(dir, rotMat);
                    user.Shoot(user.getPosition() + dir * user.GunRange);
                    user.Firing = true;
                }
                else
                    playerSquad.SquadUnits[0].Firing = false;
            }
            lastKeyInput = inputKey;
            base.HandleInput(inputKey);
        }

        /// <summary>
        /// Moves an actor in the actor map
        /// </summary>
        /// <param name="actorID">The ID of the actor to be moved</param>
        /// <param name="newPos">The new position of the actor</param>

        public void MoveActor(int actorID, Vector3 newPos)
        {
            if (actorMap.ContainsKey(actorID))
            {
                actorMap[actorID].setPosition(newPos);
            }
        }

        /// <summary>
        /// Adds an actor to the actor map
        /// </summary>
        /// <param name="newActor">The Actor to be added </param>

        public void AddActor(IActor newActor)
        {
            actorMap.Add(lastActorID, newActor);
            newActor.setID(lastActorID);
            lastActorID++;
        }

        /// <summary>
        /// Remove an actor from the actor map
        /// </summary>
        /// <param name="actorID">The ID of the actor be removed</param>

        public void RemoveActor(int actorID)
        {
            actorMap.Remove(actorID);
        }

        public void AttachProcess(CProcess newProcess)
        {
            pcsMng.Attach(newProcess);
        }

        public void InvalidateCover()
        {
            currentLevel.InvalidateCover();
            
            //Check if any of the player's squad is still in cover

            foreach (SquadActor actor in playerSquad.SquadUnits)
            {
                CoverCheck(actor);
            }

            //Check if any of the enemy squad is still in cover

            foreach (SquadActor actor in enemySquad.SquadUnits)
            {
                CoverCheck(actor);
            }
        }

        public bool IsCoverAvailable()
        {
            return currentLevel.IsCoverAvailable();
        }

        public void MakeCoverAvailable(Vector3 target)
        {
            currentLevel.MakeCoverAvailable(target);
        }

        public void CoverCheck(SquadActor actor)
        {
            actor.InCover = currentLevel.IsActorInCover(actor.getPosition());
        }

        public void DealDamage(int actorID, int damage, int radius)
        {
            if (radius > 0)
            {
                SquadActor temp;
                Vector3 center = actorMap[actorID].getPosition();
                foreach (KeyValuePair<int, IActor> pair in actorMap)
                {
                    if (pair.Value.getType().Equals("SquadActor"))
                    {
                        temp = (SquadActor)pair.Value;

                        if ((temp.getPosition().X < center.X + radius) && (temp.getPosition().X > center.X - radius) &&
                            (temp.getPosition().Z < center.Z + radius) && (temp.getPosition().Z > center.Z - radius)
                            )
                        {
                            temp.GotHit(damage);
                        }
                    }

                }
            }
            else 
            {
                ((SquadActor)actorMap[actorID]).GotHit(damage);
            }

            CheckForGameOver();
        }

        public void Explode(Vector3 explosionCenter, int damage, int radius)
        {
            SquadActor temp = null;

            foreach (KeyValuePair<int, IActor> pair in actorMap)
            {
                if (pair.Value.getType().Equals("SquadActor"))
                {
                    temp = (SquadActor)pair.Value;

                    if ((temp.getPosition().X < explosionCenter.X + radius) && (temp.getPosition().X > explosionCenter.X - radius) &&
                        (temp.getPosition().Z < explosionCenter.Z + radius) && (temp.getPosition().Z > explosionCenter.Z - radius)
                        )
                    {
                        temp.GotHit(damage);
                    }
                }

            }
        }

        public void GiveXP(int actorID, int xp)
        {
            SquadActor temp = (SquadActor)actorMap[actorID];
            temp.XP += xp;
        }

        public Queue<Node> PathFindToTarget(Vector3 actorPosition, Vector3 targetPosition, float gunRange)
        {
            NavMesh copy = new NavMesh(currentLevel.NavGraph);
            return copy.PathFind(actorPosition, targetPosition, gunRange);
        }

        public Queue<Node> PathFindToSafety(Vector3 actorPosition, Vector3 targetPosition, int safeRange)
        {
            Vector3 safety = currentLevel.FindSafeSpot(actorPosition, targetPosition, safeRange);

            NavMesh copy = new NavMesh(currentLevel.NavGraph);
            return copy.PathFind(actorPosition, safety, 0);            
        }

        public Queue<Node> PathFindToCover(Vector3 actorPosition, Vector3 targetPosition, float optimal)
        {
            Vector3? target = null;

            if (optimal > 0f)
            {
                target = currentLevel.FindOptimalCover(targetPosition, optimal);
            }
            else
            {
                target = currentLevel.FindNearestCover(actorPosition);
            }

            if (target == null)
            {
                return null;
            }

            NavMesh copy = new NavMesh(currentLevel.NavGraph);
            return copy.PathFind(actorPosition, new Vector3(target.Value.X, target.Value.Y,target.Value.Z), 0);
        }

        public void WallObstacleAvoidance(SquadActor actor)
        {
            currentLevel.WallObstacleAvoidance(actor);
        }

        public override void Update()
        {
            pcsMng.Run();

            playerSquad.VOnUpdate();
            enemySquad.VOnUpdate();

            foreach (KeyValuePair<int, IActor> pair in actorMap)
            {
                pair.Value.VOnUpdate(); ;
            }

            base.Update();
        }

        /// <summary>
        /// Draws the level tied to the gameplay screen
        /// </summary>

        public override void Draw()
        {
            base.Draw();

            SpriteBatch spriteBatch = Game1.GetSpriteBatch();
            SpriteFont spriteFont = Game1.GetSpriteFont();
            Vector2 debugPos = new Vector2(0, 0);
            Vector2 textOrigin;

            String[] debugText = new String[4];

            debugText[0] = "P1's Health: " + playerSquad.SquadUnits[0].CurrentHealth + "/" + playerSquad.SquadUnits[0].MaxHealth;
            debugText[0] += ", E1's Health: " + enemySquad.SquadUnits[0].CurrentHealth + "/" + enemySquad.SquadUnits[0].MaxHealth;
            debugText[0] += " P1's Z: " + playerSquad.SquadUnits[0].getPosition().Z;
            debugText[1] = "P2's Health: " + playerSquad.SquadUnits[1].CurrentHealth + "/" + playerSquad.SquadUnits[1].MaxHealth;
            debugText[1] += ", E1's Health: " + enemySquad.SquadUnits[1].CurrentHealth + "/" + enemySquad.SquadUnits[1].MaxHealth;
            debugText[2] = "P3's Health: " + playerSquad.SquadUnits[2].CurrentHealth + "/" + playerSquad.SquadUnits[2].MaxHealth;
            debugText[2] += ", E1's Health: " + enemySquad.SquadUnits[2].CurrentHealth + "/" + enemySquad.SquadUnits[2].MaxHealth;
            debugText[3] = "Squad State: " + playerSquad.State.ToString();
            textOrigin = spriteFont.MeasureString(debugText[0]);
            

            Game1.GetGraphicsDevice().Clear(Color.Red);

            currentLevel.Draw();

            foreach (KeyValuePair<int, IActor> pair in actorMap)
            {
                pair.Value.VRender();
            }

            if (debug)
            {
                spriteBatch.Begin();

                for (int index = 0; index < 4; index++)
                {
                    spriteBatch.DrawString(spriteFont, debugText[index], debugPos, Color.White);
                    debugPos.Y += textOrigin.Y;
                }

                spriteBatch.End();
            }
        }

        //Data Members

        ProcessManager pcsMng;

        Dictionary<int, IActor> actorMap;
        int lastActorID;

        Squad playerSquad;
        Squad enemySquad;
        Level currentLevel;
        Random rand;

        bool debug = true;
    }
}
